﻿using Artech.Common;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace Artech.Core.Wcf
{
    public class ContextFlowBehaviorAttribute: Attribute, IServiceBehavior, IEndpointBehavior, IContractBehavior
    {
        public const string MessageHeaderName = "ApplicationContext";
        public bool Suppress { get; private set; }
        public ContextFlowBehaviorAttribute(bool supress = false)
        {
            this.Suppress = supress;
        }

        #region IServiceBehavior
        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {}

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
            {
                foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
                {
                    ServiceEndpoint endpoint = serviceDescription.Endpoints.First(item => item.Address.Uri == endpointDispatcher.EndpointAddress.Uri &&
                        item.Contract.Name == endpointDispatcher.ContractName && item.Contract.Namespace == endpointDispatcher.ContractNamespace);
                    foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
                    {
                        if (operation.CallContextInitializers.OfType<ContextReceiver>().Any())
                        {
                            continue;
                        }
                        OperationBehaviorAttribute attribute = endpoint.Contract.Operations.First(op => op.Name == operation.Name).Behaviors.Find<OperationBehaviorAttribute>();
                        attribute = attribute ?? new OperationBehaviorAttribute { TransactionScopeRequired = false };
                        operation.CallContextInitializers.Add(new ContextReceiver("MessageHeaderName", Constants.Namespaces.ContextHeader, attribute.TransactionScopeRequired));
                    }
                }
            }
        }

        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {

        }
        #endregion

        #region IEndpointBehavior
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {}

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            this.SetClientMessageFormatter(endpoint, clientRuntime);
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
            {
                if (operation.CallContextInitializers.OfType<ContextReceiver>().Any())
                {
                    continue;
                }
                OperationBehaviorAttribute attribute = endpoint.Contract.Operations.First(op => op.Name == operation.Name).Behaviors.Find<OperationBehaviorAttribute>();
                attribute = attribute ?? new OperationBehaviorAttribute { TransactionScopeRequired = false };
                operation.CallContextInitializers.Add(new ContextReceiver(MessageHeaderName, Constants.Namespaces.ContextHeader, attribute.TransactionScopeRequired));
            }
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }
        #endregion

        #region IContractBehavior
        public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            this.SetClientMessageFormatter(endpoint, clientRuntime);
        }

        public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
        {
            foreach (DispatchOperation operation in dispatchRuntime.Operations)
            {
                if (operation.CallContextInitializers.OfType<ContextReceiver>().Any())
                {
                    continue;
                }
                OperationBehaviorAttribute attribute = contractDescription.Operations.First(op => op.Name == operation.Name).Behaviors.Find<OperationBehaviorAttribute>();
                attribute = attribute ?? new OperationBehaviorAttribute { TransactionScopeRequired = false };
                operation.CallContextInitializers.Add(new ContextReceiver(MessageHeaderName, Constants.Namespaces.ContextHeader, attribute.TransactionScopeRequired));
            }
        }

        public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
        {}
        #endregion

        protected virtual void SetClientMessageFormatter(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            foreach (OperationDescription operation in endpoint.Contract.Operations)
            {
                if (operation.Behaviors.Find<SupressContextFlowAttribute>() != null)
                {
                    continue;
                }
                IOperationBehavior serializerBehavior = operation.Behaviors.Find<XmlSerializerOperationBehavior>();
                if (null == serializerBehavior)
                {
                    serializerBehavior = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
                }
                ClientOperation clientOperation = clientRuntime.Operations[operation.Name];
                serializerBehavior.ApplyClientBehavior(operation, clientOperation);
                clientOperation.Formatter = new ContextSender(MessageHeaderName, Constants.Namespaces.ContextHeader, clientOperation.Formatter);
            }
        }
    }
}
